package de.fh.ffm.elevator;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 
 * This class has knowledge of all floors and the elevator and manages calls
 * from buttons. If a floorbutton is pushed, the destination-list of the
 * elevator gets updated and the elevator moves to this floor. If an
 * elevatorbutton is pushed, the elevator moves to the other floor.
 * 
 */
public class ElevatorManager {

	/**
	 * Exception that is thown if you try to delete a floor from an empty
	 * elevators destination-list.
	 * 
	 * @author claudio
	 * 
	 */
	public class ElevatorsDestinationListIsEmpty extends RuntimeException {
		private static final long serialVersionUID = 1L;
	}

	private Elevator elevator;
	private List<Floor> floors;
	private List<Floor> elevatorDestinationList;

	private final Map<Elevator, Floor> elevatorsCurrentFloor;
	private final Map<Elevator, List<Floor>> elevatorsDestinations;
	private ArrivalStrategy arrivalStrategy;

	/**
	 * 
	 * An initially emtpy destination-list for the elevator is created. The
	 * elevator-manager of all floor-buttons is set to this.
	 * 
	 * @param elevator
	 *            The elevator that is managed.
	 * @param floors
	 *            A list of all the floors the elevator can move to.
	 */
	public ElevatorManager ( final Elevator elevator, final List<Floor> floors ) {

		this.elevator = elevator;
		this.floors = new ArrayList<Floor>(floors);

		elevatorDestinationList = new ArrayList<Floor>();

		elevatorsCurrentFloor = new HashMap<Elevator, Floor>();

		elevatorsDestinations = new HashMap<Elevator, List<Floor>>();

		elevatorsDestinations.put(elevator, elevatorDestinationList);

		for ( int i = 0; i < floors.size(); i++ ) {
			floors.get(i).getButton().setElevatorManager(this);
		}

		elevator.getButton().setElevatorManager(this);

	}

	/**
	 * This method calls an Elevator to an specific floor. First the elevator
	 * leaves the Floor it belongs to and after that the elevator moves to his
	 * destination floor by using an ArrivalStrategy.
	 * 
	 * @param floor
	 *            The Floor the elevator is called to.
	 */
	public void callElevatorToFloor ( Floor floor ) {
		elevatorsDestinations.get(elevator).add(floor);

		if ( elevator.getFloor() != null ) {

			elevator.leavesFloor();

			arrivalStrategy.elevatorLeftFor(elevator,
					getCurrentFloorForElevator(elevator), floor);
		}
	}

	/**
	 * This method returns the FloorPosition from a specific elevator.
	 * 
	 * @param e
	 *            The specific elevator whichs position you want from.
	 * @return The Floor the elevator is at
	 */
	public Floor getCurrentFloorForElevator ( Elevator e ) {
		return elevatorsCurrentFloor.get(e);
	}

	/**
	 * This method returns the FestiantionFloor of an specific elevator, means
	 * the elevator he is moving to.
	 * 
	 * @param e
	 *            The specific elevator whichs destination you want from.
	 * @return The Floor the elevator is moving to.
	 */
	public Floor getDestinationFloorForElevator ( Elevator e ) {
		return elevatorsDestinations.get(e).get(0);
	}

	/**
	 * 
	 * This method return a List of Floor that the Elevator will move to.
	 * 
	 * @param e
	 *            The specific elevator whichs destination-list you want from.
	 * @return An arraylist with floors.
	 */
	public List<Floor> getDestionationListForElevator ( Elevator e ) {
		return elevatorsDestinations.get(e);
	}

	/**
	 * This method lets an elevator arrive at a certain floor. After arrving the
	 * position of the floor is updated.
	 * 
	 * @param e
	 *            The arrving elevator.
	 * @param floor
	 *            The floor the elevator will arrive at.
	 */
	public void elevatorArrivedAtFloor ( Elevator e, Floor floor ) {
		elevatorsCurrentFloor.put(e, floor);
		removeFirstDestinationForElevator(e);
		e.arrivesAtFloor(floor);
	}

	/**
	 * This method removes the first destination of an elevators destination
	 * list.
	 * 
	 * @param e
	 *            The elevator whichs destination you want to delete from.
	 */
	public void removeFirstDestinationForElevator ( Elevator e ) {
		if ( getDestionationListForElevator(e).isEmpty() ) {
			throw new ElevatorsDestinationListIsEmpty();
		}
		elevatorsDestinations.get(e).remove(0);
	}

	/***
	 * In an two-floor elevator system this method is used to determine the
	 * floor the elevator ist not at.
	 * 
	 * @param elevator
	 *            The elevator you use in this two-floor system.
	 * @return If the elevator is at floor 0 you get floor 1. Vice versa you get
	 *         floor 0 if the elvator is at floor 0.
	 */
	public Floor getOtherFloorForElevator ( Elevator elevator ) {
		if ( elevator.getFloor().equals(floors.get(0)) )
			return floors.get(1);
		if ( elevator.getFloor().equals(floors.get(1)) )
			return floors.get(0);
		return null;
	}

	/**
	 * This method sets an Arrival Strategy for the Elevator Manager.
	 * 
	 * @param as
	 *            The Arrival Strategy you want to set for the ElevatorManager.
	 */
	public void setArrivalStrategy ( ArrivalStrategy as ) {
		arrivalStrategy = as;
	}

}
